import pygame,os,random,sqlite3,ConfigParser

# init general
config = ConfigParser.RawConfigParser()
con = sqlite3.connect('piczle.db3')
cur = con.cursor()

# init pygame
pygame.init()
screen=pygame.display.set_mode((1024,768),pygame.FULLSCREEN)
clock=pygame.time.Clock()
main_dir=os.path.split(os.path.abspath(__file__))[0]
data_dir=os.path.join(main_dir, 'data')

# define colors
white=(255,255,255)
black=(0,0,0)
red=(255,0,0)
green=(0,255,0)
blue=(0,0,255)
grey=(204,204,204)
gold=(164,169,79)
cyan=(87,231,112)

butStart_color=black
butStart_label='shuffle'
butPause_color=black
butPause_label='pause'
butOptions_color=black
butOptions_label='options'
butHelp_color=black
butAbout_color=black
butScores_color=black
butScores_label='scores'
butExit_color=black
butPlus1_color=white
butMinus1_color=white
butPlus2_color=white
butMinus2_color=white

config.read('piczle.cfg')
ndim = config.getint('Dims', 'ndim')
mdim = config.getint('Dims', 'mdim')

inv=0 #inversions
size=50
offset,boffset,moffset,ioffset=10,50,550,700
bwidth,bheight,iwidth,iheight=140,40,300,340
started=False
paused=False
apply=False
scores=False

ttime,tclicks=0,0

# containers
p=[]
win=[]
scn=[]
pzl=[]
rpzl=[]

# strings
sInfo=[]
sInfo.append('SHUFFLE / ABORT')
sInfo.append('')
sInfo.append('Shuffle the puzzle to start a')
sInfo.append('game. The timer will begin and')
sInfo.append('mouse clicks will be recorded.')
sInfo.append('Click abort to end the game in')
sInfo.append('progress. Aborting a game')
sInfo.append('stops the timer and scores are')
sInfo.append('not recorded. The other menu')
sInfo.append('options are not available once')
sInfo.append('a game has started.')
sInfo.append('PAUSE / RESUME')
sInfo.append('')
sInfo.append('This is used to pause a game in')
sInfo.append('progress. The board will be')
sInfo.append('hidden and the timer stopped.') 
sInfo.append('Click resume to continue the')
sInfo.append('paused game. The other menu')
sInfo.append('options are not available once')
sInfo.append('a game has been paused.')
sInfo.append('')
sInfo.append('')
sInfo.append('OPTIONS / APPLY')
sInfo.append('')
sInfo.append('Set the board size and other')
sInfo.append('options. These options cannot')
sInfo.append('be changed once a game has')
sInfo.append('started. Click the apply button')
sInfo.append('to activate changed options.')
sInfo.append('# Cols = Number of columns.')
sInfo.append('# Rows = Number of rows.')
sInfo.append('Click +/- to change dimensions.')
sInfo.append('')
sInfo.append('HELP')
sInfo.append('')
sInfo.append('The goal is to return shuffled')
sInfo.append('puzzles back to their original')
sInfo.append('order using the least number of')
sInfo.append('mouse clicks and in the shortest')
sInfo.append('time. Clicking a piece adjacent')
sInfo.append('the blank piece will move that')
sInfo.append('piece into the blank space.')
sInfo.append('Green menu buttons will ')
sInfo.append("respond but red ones won't.")
sInfo.append('ABOUT')
sInfo.append('')
sInfo.append('pixcle v 1.0')
sInfo.append('freeware 2009 freevryheid')
sInfo.append('')
sInfo.append('Thanks to Angie for help with')
sInfo.append('some of the graphics.')
sInfo.append('This software was developed')
sInfo.append('using pygame. The source code')
sInfo.append('is placed in the public')
sInfo.append('domain.')
sInfo.append('SCORES / RESUME')
sInfo.append('')
sInfo.append('Scoring is done considering the')
sInfo.append('difficulty of the puzzle taking')
sInfo.append('into account time and number of')
sInfo.append('clicks to complete the puzzle.')
sInfo.append('Scores for each possible')
sInfo.append('combination of puzzle')
sInfo.append('configuration are recorded.')
sInfo.append('Click resume to continue as some')
sInfo.append('menus are disabled on scores.')
sInfo.append('EXIT')
sInfo.append('')
sInfo.append('This game exits without prompt!')
sInfo.append('If a game is in progress it')
sInfo.append('will be aborted and not scored.')
sInfo.append('Alternatively, hit the ESC key')
sInfo.append('to immediately exit the game - ')
sInfo.append('no questions asked :)')
sInfo.append('')
sInfo.append('')
sInfo.append('')

# fonts
ff=os.path.join(data_dir, 'whitrabt.ttf')
smFont=pygame.font.Font(ff,30)
vmFont=pygame.font.Font(ff,15)

# images
fg=os.path.join(data_dir, 'glass_mask.png')
glass=pygame.image.load(fg).convert_alpha()

def SetContainers():
    global p,win,scn,pzl,rpzl
    p=[]
    win=[]
    scn=[]
    pzl=[]
    rpzl=[]
    for i in range(ndim*mdim):
        p.append(i)
    win=p[:]

# make buttons
def Button(surf,rect):
    newsurf=surf.subsurface(rect)  
    newrect=pygame.Rect(newsurf.get_offset(),newsurf.get_size())
    return newsurf,newrect

butStart,rectStart      =Button(screen,((moffset,offset+0*boffset),(bwidth,bheight)))
butPause,rectPause      =Button(screen,((moffset,offset+1*boffset),(bwidth,bheight)))
butOptions,rectOptions  =Button(screen,((moffset,offset+2*boffset),(bwidth,bheight)))
butHelp,rectHelp        =Button(screen,((moffset,offset+3*boffset),(bwidth,bheight)))
butAbout,rectAbout      =Button(screen,((moffset,offset+4*boffset),(bwidth,bheight)))
butScores,rectScores    =Button(screen,((moffset,offset+5*boffset),(bwidth,bheight)))
butExit,rectExit        =Button(screen,((moffset,offset+6*boffset),(bwidth,bheight)))
butInfo,rectInfo        =Button(screen,((ioffset,offset+0*boffset),(iwidth,iheight)))
butTime,rectTime        =Button(screen,((moffset,offset+8*boffset),(bwidth+40,bheight)))
butClick,rectClick      =Button(screen,((moffset+bwidth+60,offset+8*boffset),(bwidth+60,bheight)))
butHide,rectHide        =Button(screen,((0,0),(550,550)))
butPlus1,rectPlus1      =Button(screen,((710,70),(10,10)))
butMinus1,rectMinus1    =Button(screen,((730,70),(10,10)))
butPlus2,rectPlus2      =Button(screen,((710,90),(10,10)))
butMinus2,rectMinus2    =Button(screen,((730,90),(10,10)))

def MakePuzzle():
    butHide.fill(black)
    SetContainers()
    # draw pieces
    j=1
    for y in range(mdim):
        for x in range(ndim):
            screen.blit(glass, (x*size+offset,y*size+offset))
            m=str(j)
            color=cyan
            s = smFont.size(str(j))
            sx=(size-s[0])/2.
            sy=(size-s[1])/2.
            screen.blit(smFont.render(m,1,color),(x*size+sx+offset,y*size+sy+offset))
            j=j+1
            if j>ndim*mdim-1: break
    # make puzzle pieces - store these in scn
    j=0
    for y in range(mdim):
        for x in range(ndim):
            scn.append(pygame.Surface((size,size)))
            scn[j].blit(screen,(0,0),((x*size+offset,y*size+offset),(size,size)))
            j=j+1
    # define puzzle rects
    j=0
    for y in range(mdim):
        for x in range(ndim):
            pzl.append(screen.subsurface((x*size+offset,y*size+offset),(size,size)))
            rpzl.append(pygame.Rect(pzl[j].get_offset(),pzl[j].get_size()))
            j=j+1

MakePuzzle()
                                
def DrawButton(surf,tit,c):
    surf.fill(c)
    pygame.draw.rect(surf,white,surf.get_rect(),3)
    s=smFont.size(tit)
    bx=(bwidth-s[0])/2.
    by=(bheight-s[1])/2.
    surf.blit(smFont.render(tit,1,blue),(bx,by))

def DrawInfo(info):
    butInfo.fill(black)
    pygame.draw.rect(butInfo,white,butInfo.get_rect(),3)
    if (info=='start'):
        j=0
    elif (info=='pause'):
        j=11
    elif (info=='options'):
        j=22
    elif (info=='help'):
        j=33
    elif (info=='about'):
        j=44
    elif (info=='scores'):
        j=55
    else:
        j=66
    for txt in range(11):
        s=vmFont.size(sInfo[txt+j])
        bx=(iwidth-s[0])/2.
        by=20*txt+20
        butInfo.blit(vmFont.render(sInfo[txt+j],1,white),(bx,by))

def DrawTimeClicks(surf,tit):
    surf.blit(smFont.render(tit,1,red),(0,0))

def DrawOptions():
    butInfo.fill(black)
    pygame.draw.rect(butInfo,white,butInfo.get_rect(),3)
    s=vmFont.size('SET OPTIONS')
    bx=(iwidth-s[0])/2.
    by=20
    butInfo.blit(vmFont.render('SET OPTIONS',1,white),(bx,by))
    by=by+40
    butInfo.blit(vmFont.render(' # Cols: '+str(ndim),1,white),(40,by))
    vmFont.set_bold(1)
    butPlus1.blit(vmFont.render('+',1,butPlus1_color),(0,0))
    butMinus1.blit(vmFont.render('-',1,butMinus1_color),(0,0))
    by=by+20
    vmFont.set_bold(0)
    butInfo.blit(vmFont.render(' # Rows: '+str(mdim),1,white),(40,by))
    vmFont.set_bold(1)
    butPlus2.blit(vmFont.render('+',1,butPlus2_color),(0,0))
    butMinus2.blit(vmFont.render('-',1,butMinus2_color),(0,0))
    vmFont.set_bold(0)

def DrawScores():
    sql='select count(tdate),min(ttime)/1000,min(tclicks),max(ttime)/1000,max(tclicks),avg(ttime)/1000,avg(tclicks) from scores where ndim='+str(ndim)+' and mdim='+str(mdim)+';'
    cur.execute(sql)
    num,mint,minc,maxt,maxc,avgt,avgc=cur.fetchone()
    butInfo.fill(black)
    pygame.draw.rect(butInfo,white,butInfo.get_rect(),3)
    s=vmFont.size('SCORES')
    bx=(iwidth-s[0])/2.
    by=20
    butInfo.blit(vmFont.render('SCORES',1,white),(bx,by))
    by=by+40
    butInfo.blit(vmFont.render('COLS: '+str(ndim),1,white),(40,by))
    by=by+20
    butInfo.blit(vmFont.render('ROWS: '+str(mdim),1,white),(40,by))
    by=by+20
    butInfo.blit(vmFont.render('NUMS: '+str(num),1,white),(40,by))
    by=by+40
    txt='\t\t\t\t\t\t\t\tTIME\t\t\t\tCLICKS'
    txt.expandtabs()
    butInfo.blit(vmFont.render(txt,1,white),(40,by))
    by=by+20
    if num == 0:
        mint=0
        minc=0
        maxt=0
        maxc=0
        avgt=0
        avgc=0
    txt='MIN\t\t\t\t\t'+str(mint)+'\t\t\t\t\t\t'+str(minc)
    txt.expandtabs()
    butInfo.blit(vmFont.render(txt,1,white),(40,by))
    by=by+20
    txt='MAX\t\t\t\t\t'+str(maxt)+'\t\t\t\t\t\t'+str(maxc)
    txt.expandtabs()
    butInfo.blit(vmFont.render(txt,1,white),(40,by))
    by=by+20
    txt='AVG\t\t\t\t\t'+str(int(avgt))+'\t\t\t\t\t\t'+str(int(avgc))
    txt.expandtabs()
    butInfo.blit(vmFont.render(txt,1,white),(40,by))

# check if mouse is in Rect r
def MouseInRect(r):
    m = pygame.mouse.get_pos()
    if (m[0]>r.left and m[0]<r.right):
        if (m[1]>r.top and m[1]<r.bottom):
            return True
    return False

# http://kevingong.com/Math/SixteenPuzzle.html
def GetValid(p):
    global inv
    invalid=True
    while invalid:
        random.shuffle(p)
        rem=ndim*mdim-1
        vp=[]
        vp=p[:]
        ip=[]
        ip=p[:]
        ip.reverse()
        c=int((ip.index(rem))/ndim)+1
        vp.remove(rem)
        #calc inversions
        inv=0
        for v in vp:
            for i in range(rem):
                if (vp.index(v)>=i):
                    continue
                if (v>vp[i]):
                    inv=inv+1
        if (ndim%2==0):
            if (c%2!=0):
                if (inv%2==0):
                    invalid=False
            else:
                if (inv%2!=0):
                    invalid=False
        else:
            if (inv%2==0):
                invalid=False

# shuffle puzzle pieces
def ShufflePuzzle():
    GetValid(p)
    j=0
    for y in range(mdim):
        for x in range(ndim):
            screen.blit(scn[p[j]],(x*size+offset,y*size+offset))
            j=j+1

def ResetPuzzle():
    j=0
    for y in range(mdim):
        for x in range(ndim):
            screen.blit(scn[win[j]],(x*size+offset,y*size+offset))
            j=j+1

def ResumePuzzle():
    j=0
    for y in range(mdim):
        for x in range(ndim):
            screen.blit(scn[p[j]],(x*size+offset,y*size+offset))
            j=j+1

def FindRect(r):
#    pdb.set_trace()
    for y in range(mdim):
        j=y*ndim
        if (r.top==y*size+offset):
            for x in range(ndim):
                if (r.left==x*size+offset):
                    return j
                j=j+1

def CheckMove(n):
    rem=ndim*mdim-1
    # check left
    if (n%ndim!=0):
        if (p[n-1]==rem):
            return True,(n-1)
    # check right
    if ((n+1)%ndim!=0):
        if (p[n+1]==rem):
            return True,(n+1)
    # check top
    if (n>ndim-1):
        if (p[n-ndim]==rem):
            return True,(n-ndim)
    # check bottom
    if (n<rem-ndim+1):
        if (p[n+ndim]==rem):
            return True,(n+ndim)
    return False,9           

# todo: sqlite3 db of wins 
def Winner():
    global started,butStart_label
    sql='insert into scores (NDIM,MDIM,INV,TTIME,TCLICKS) values ('+str(ndim)+','+str(mdim)+','+str(inv)+','+str(ttime)+','+str(tclicks)+');'
    cur.execute(sql)
    con.commit()
    started=False
    butStart_label='shuffle'
    print 'Winner'


def Swap(m,n):
    global btime,bclicks,started
    screen.blit(scn[p[m]],rpzl[n].topleft)
    screen.blit(scn[p[n]],rpzl[m].topleft)
    d=p[m]
    p[m]=p[n]
    p[n]=d
    if p==win:
        Winner()

# main loop
mainloop=True
while mainloop:
    tt=clock.tick(30)
    DrawButton(butStart,butStart_label,butStart_color)
    DrawButton(butPause,butPause_label,butPause_color)
    DrawButton(butOptions,butOptions_label,butOptions_color)
    DrawButton(butHelp,'help',butHelp_color)
    DrawButton(butAbout,'about',butAbout_color)
    DrawButton(butScores,butScores_label,butScores_color)
    DrawButton(butExit,'exit',butExit_color)
    butTime.fill(black)
    butClick.fill(black)
    DrawTimeClicks(butTime,'time: '+str(ttime/1000))
    DrawTimeClicks(butClick,'clicks: '+str(tclicks))
    if (apply):
        DrawOptions()
    if (started and not paused):
        ttime = ttime + tt
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            mainloop=False
        elif event.type==pygame.KEYDOWN:
            if event.key==pygame.K_ESCAPE:
                mainloop=False
        elif event.type==pygame.MOUSEMOTION:
            if (apply):
                if MouseInRect(rectPlus1):
                    butPlus1_color=green
                else:
                    butPlus1_color=white
                if MouseInRect(rectMinus1):
                    butMinus1_color=green
                else:
                    butMinus1_color=white
                if MouseInRect(rectPlus2):
                    butPlus2_color=green
                else:
                    butPlus2_color=white
                if MouseInRect(rectMinus2):
                    butMinus2_color=green
                else:
                    butMinus2_color=white
            if MouseInRect(rectStart):
                if (paused or apply):
                    butStart_color=red
                else:
                    butStart_color=green
                if (not apply and not scores):
                    DrawInfo('start')
            else:
                butStart_color=black
            if MouseInRect(rectPause):
                if (started):
                    butPause_color=green
                else:
                    butPause_color=red
                if (not apply and not scores):
                    DrawInfo('pause')
            else:
                butPause_color=black
            if MouseInRect(rectOptions):
                if (started or scores):
                    butOptions_color=red
                else:    
                    butOptions_color=green
                if (not apply and not scores):
                    DrawInfo('options')
            else:
                butOptions_color=black
            if MouseInRect(rectHelp):
                if (apply or scores):
                    butHelp_color=red
                else:    
                    butHelp_color=green
                if (not apply and not scores):
                    DrawInfo('help')
            else:
                butHelp_color=black
            if MouseInRect(rectAbout):
                if (apply or scores):
                    butAbout_color=red
                else:
                    butAbout_color=green
                if (not apply and not scores):
                    DrawInfo('about')
            else:
                butAbout_color=black
            if MouseInRect(rectScores):
                if (apply):
                    butScores_color=red
                else:
                    butScores_color=green
                if (not apply and not scores):
                    DrawInfo('scores')
            else:
                butScores_color=black
            if MouseInRect(rectExit):
                butExit_color=green
                if (not apply and not scores):
                    DrawInfo('exit')
            else:
                butExit_color=black
        elif event.type==pygame.MOUSEBUTTONDOWN:
            if (apply):
                if MouseInRect(rectPlus1):
                    ndim=ndim+1
                    if ndim>10:
                        ndim=10
                if MouseInRect(rectMinus1):
                    ndim=ndim-1
                    if ndim<3:
                        ndim=3
                if MouseInRect(rectPlus2):
                    mdim=mdim+1
                    if mdim>10:
                        mdim=10
                if MouseInRect(rectMinus2):
                    mdim=mdim-1
                    if mdim<3:
                        mdim=3
            if MouseInRect(rectExit):
                mainloop=False
            if MouseInRect(rectStart):
                if (paused or apply):
                    break
                if (not started):
                    started=True
                    butStart_label='abort'
                    ShufflePuzzle()
                    ttime=0
                    tclicks=0
                else:
                    started=False
                    butStart_label='shuffle'
                    ResetPuzzle()
                    ttime=0
                    tclicks=0
            if MouseInRect(rectPause):
                if (started and not paused):
                    paused=True
                    butHide.fill(black)
                    butPause_label='resume'
                else:
                    if (started):
                        ResumePuzzle()
                    paused=False
                    butPause_label='pause'
            if MouseInRect(rectOptions):
                if (started or scores):
                    break
                if (apply):
                    apply=False
                    butOptions_label='options'
                    config.set('Dims', 'ndim', str(ndim))
                    config.set('Dims', 'mdim', str(mdim))
                    with open('piczle.cfg', 'wb') as configfile:
                        config.write(configfile)
                    MakePuzzle()
                else:
                    apply=True
                    butOptions_label='apply'
            if MouseInRect(rectScores):
                if (apply):
                    break
                if (scores):
                    scores=False
                    butScores_label='scores'
                else:
                    scores=True
                    butScores_label='resume'
                    DrawScores()    
            if (started and not paused):
                for rp in rpzl:
                    if MouseInRect(rp):
                        m=FindRect(rp)
                        move,n=CheckMove(m)
                        if (move):
                            tclicks=tclicks+1
                            Swap(m,n)                
    pygame.display.update()
    
con.close()
pygame.quit()

